总概
在做公司项目过程中遇到一些需要进行替换原来方法情况。小编对 Objc 在消息发送和转发过程整理,针对在消息发送和转发在 runtime 层实现和提出的方法替换方案原理做出详细说明和记录。
下面是小编本博文的写作思路:
iOS 消息发送和转发过程
想要对现有的 Method 进行替换找到比较可行的方案,其一可以从方法在执行过程中考虑。下面小编将整理的消息发送和转发的详细步骤进行整理。
Objc 的动态特性
Objc 是基于 C 和 Smalltalk 为基础实现面向对象开发的动态语言,Objc具有特色一点就是采纳了 Smalltalk 语言消息的特性,所以我们在 Objc 中经常使用 [receiver message] 来进行消息传递。Objc 的动态语言的特性可以根据 NSObject方式之一来实现交互,下面看相关 API (下面均可进行动态判断):
1 | + (NSString *)description;//需要重载并为定义的类进行描述 |
iOS 消息发送转发机制
在 Objc 使用 Foundation 中 NSObject 中实现动态特性中,上述列出消息发送和转发过程的调用的 API。在整体消息从发送转发可以分为以下三个时段:
- 动态解析
- 快速转发
- 慢速转发
下图是消息发送和转发过程三个时段流程,后面给出详细说明:
下面是在 iOS 方法调用实现:
1 | //未实现方法调用 |
动态解析
如果要实现动态解析就要使用下面的方法:
1 | + (BOOL)resolveClassMethod:(SEL)sel;//解析类方法选择子 |
当我们在实现 [receiver message] 在没有相关实现的情况下,系统就会沿着 resolveInstanceMethod: 顺着继承向上查找相关 message 实现方法,如果没有找到相关实现就会返回 NO 进行下一步操作。在这个过程中我们可以通过动态加载的方式,实现我们预先实现的方法。
动态加载实现代码如下:
1 | + (BOOL)resolveInstanceMethod:(SEL)sel { |
从打印的结果来看:我们通过动态加载的方式实现预先方法的调用。但是在打印中 SEL 还是原来的选择子,后面会讲解。
从上面的解决方式实现来看,严格意义上来说动态解析不是消息的转发。因为在指向过程中消息的接受的对象还是
receiver, 只是把指向message的IMP的指针加入到receiver的方法列表。
快速转发
快速转发相当于我们在请求网页是重新定向。receiver 对象和其继承父类没有找到相应的实现方法,而把实现的转向其他的对象。
快速转发实现代码如下:
1 | //重定向的实现类 |
更具上面打印的结果:看出快速转发可以实现当前
receiver方法列表找不到相关的message的IMP,可以通过制定其他的对象来进行实现。
慢速转发
慢速转发过程中使用两个对象:NSMethodSignature 和 NSInvocation
NSMethodSignature: 对象定义了方法的签名,签名中包含:方法的参数和返回值。NSInvocation: 保存了目标、选着子和参数等消息调用的必须的全部元素。
在转发过程中要是到到下面 API:
1 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;//在协议中实现,返回相关的方法签名 |
下面先直接把慢速转发的代码列出来:
1 | //重定向的实现类 |
快速转发失败后,运行时系统先调用
methodSignatureForSelector:返回一个方法签名用于创建NSInvocation对象,创建好后调用forwardInvocation:方法,在改方法中NSInvocation对象将调用invokeWithTarget:方法唤醒新接受者中的同名方法。
iOS 发送和转发小结
在消息发送和转发过程中没有发现实现 message 的 IMP 过程中三步骤中均可以实现相关的消息添加,也可以在三者中进行相关的方法替换。
但是三者之间还是有各自的特点:
- 动态解析 : 适用于将原来的类中的方法替换掉或者延迟加载。
- 快速转发 : 可以将消息处理转发给其他对象,使用范围更广,不只是限于原来的对象。
- 慢速转发 : 跟快转发一样可以消息转发,但它能通过NSInvocation对象获取更多消息发送的信息,例如:target、selector、arguments和返回值等信息。
iOS 消息 runtime 层实现过程
小编在 iOS 消息发送和转发根据 NSObject 方面讲述 [receiver message] 消息调用处理,但是在 runtime 层在怎样情形呢?
[receiver message] 这句的含义是:向 receiver 发送名为 message 的消息。
打开开发文档(在 objc/message.h 中)
1 | [receiver message]; |
是下面的表示方式:
1 | //objc_msgSend(self, Selector);//基本表示方法 |
其中 objc_msgSend 的真正实现代码如下:
1 | objc_msgSend(id _Nullable self, SEL _Nonnull op, ...); |
小编下面简单的介绍下
objc_msgSend中的参数和如何使用runtime实现方法替换。
id
上面在 objc_msgSend 中的第一参数 id。 小编认为对于 id 用该不会陌生,id 是指向类实例一个指针。
1 | typedef struct objc_object *id; |
Objc_object中包含一个 isa 的指针, isa 是 isa_t 的联合体。根据 isa 指针可以获取对象所属的类。
SEL
objc_msgSend 中的第二个参数 SEL,在 Objc 中变现为 selector。selector是方法的选择器,可以视作系统用来区分方法的 ID,而此 ID 的类型为 SEL结构。
1 | typedef struct objc_selector *SEL; |
最本质的就是 SEL 就是映射方法的字符串,小编记得可以在 objc 中 @selector() 和 runtime 中的 objc_registerName 来获取方法选着器。
注:在不同类中方法名相同的
SEL相同,在同一个类中的方法名相同参数不同的SEL相同。
动态实现方法替换
1 | - (BOOL)swapMethods { |
上面是使用 runtime 的形式用新的方法替换掉原来的方法,其中使用到的知识会在 runtime 详解中解析讲解。
提出替换方案
在替换方法方案中,目前有 4 种:
- 在消息动态解析中替换方式 :动态解析替换的基础是没有找到实现文件,需要对实现进行屏蔽
- 使用
runtime实现动态加载替换方式 :需要修改原来方法的名字或者修改参数 - 使用子类继承的方式 :不需要修改任何信息,但是如果在有子类情况下改动比较麻烦
- 使用
Category方式 :不需要改动名字和参数,但是会引入新的文件数目
小结
在替换方法过程中要根据具体的情景来进行选择合适的替换方式。
参考资料:
Message forwarding
Objective-c-messaging
The faster objc_msgSend
Understanding objective-c runtime
objc4/objc4-709
objc-private.h